﻿using System;
using System.Collections.Generic;
using System.Configuration;
using PSE.ProcessManager.Common.Gateway;
using System.ServiceModel.Description;
using PSE.ProcessManager.Common.Process;
using System.ServiceModel;
using System.Xml.Serialization;
using PSE.ProcessManager.Entity;
using System.Threading;
using PSE.Framework.Threading;
using PSE.Framework.Configuration;
using PSE.ProcessManager.Common.Configuration;

namespace PSE.ProcessManager.Client.Process
{
    /// <summary>
    /// Classe base para o client do processo.
    /// </summary>
    public class BaseProcess : IDisposable
    {
        #region Properties

        #region Private Members

        protected SenderGateway _inteligenceManagerGateway;
        private const string _inteligenceManagerAddressKey = "InteligenceManagerAddress";
        private const int _finalizeMessageId = 2;
        private const int _abortMessageId = 7;
        private const int _abortExecutedMessageId = 8;
        protected const int _errorMessageId = 4;


        #endregion

        /// <summary>
        /// Id do Produto
        /// </summary>
        protected string ProductId
        {
            get;
            set;
        }

        /// <summary>
        /// Id do Processo
        /// </summary>
        protected int Id;
        /// <summary>
        /// Indica se o processo cliente esta conectado ao Inteligence Manager
        /// </summary>
        private bool _isConnected = false;

        public bool IsConnected
        {
            get { return _isConnected; }
        }
        /// <summary>
        /// Identificação do Processo Client
        /// </summary>
        protected string ClientIdentifier = Guid.NewGuid().ToString();

        #endregion

        #region Events

        /// <summary>
        /// Evento disparado ao receber uma mensagem.
        /// </summary>
        [Obsolete("Para receber mensagens faça override do método virtual OnMessageReceived")]
        ServiceEvents.MessageReceived onReceiveMessage;

        /// <summary>
        /// Evento disparado ao receber uma mensagem Callback do ReceiverGateway
        /// </summary>
        [Obsolete("Para receber mensagens faça override do método virtual OnMessageReceived")]
        protected ServiceEvents.MessageReceived OnReceiveMessage
        {
            set
            {
                if (_inteligenceManagerGateway != null)
                    _inteligenceManagerGateway.OnReceiveMessage += value;
            }
        }

        /// <summary>
        /// Os eventos disparados pelo ProcessClient são executados dentro da thread de callback do WCF,
        /// por esse motivo se alguem executar uma chamada de um serviço do ProcessManager de dentro de
        /// um evento (ex: Chamar o IsProcessRunnig dentro do evento ProcessThrowAnException) vai ocorrer 
        /// um deadlock. Para resolver o problema os eventos são enfileirados e disparados assincronamente
        /// pela classe abaixo.
        /// </summary>
        ExecutionQueue _executionQueue = new ExecutionQueue();

        /// <summary>
        /// Ocorre quando a conexão ao Inteligence Manager é efetuada
        /// com sucesso
        /// </summary>
        public event EventHandler Connected;

        protected virtual void OnConnected(EventArgs e)
        {
            _isConnected = true;

            _executionQueue.QueueUserWorkItem(
                new WaitCallback(
                    state =>
                    {
                        if (Connected != null)
                            Connected(this, e);
                    }
                ));
        }

        /// <summary>
        /// Ocorre quando a desconexão com o Inteligence Manager é efetuada
        /// com sucesso
        /// </summary>
        public event EventHandler Disconnected;

        protected virtual void OnDisconnected(EventArgs e)
        {
            _isConnected = false;

            _executionQueue.QueueUserWorkItem(
                new WaitCallback(
                    state =>
                    {
                        if (Disconnected != null)
                            Disconnected(this, e);
                    }
                ));
        }

        /// <summary>
        /// Ocorre quando o processo é inicializado
        /// </summary>
        public event EventHandler Initialized;

        protected virtual void OnInitialized(EventArgs e)
        {
            _executionQueue.QueueUserWorkItem(
                new WaitCallback(
                    state =>
                    {
                        if (Initialized != null)
                            Initialized(this, e);
                    }
                ));
        }

        /// <summary>
        /// Ocorre quando o processo é finalizado.
        /// </summary>
        public event EventHandler Finalized;

        protected virtual void OnFinalized(EventArgs e)
        {
            _executionQueue.QueueUserWorkItem(
                new WaitCallback(
                    state =>
                    {
                        if (Finalized != null)
                            Finalized(this, e);
                    }
                ));
        }

        /// <summary>
        /// Ocorre quando uma exceção é disparada pelo Processo Server.
        /// </summary>
        public event EventHandler<ExceptionEventArgs> ProcessThrowAnException;

        protected virtual void OnProcessThrowAnException(ExceptionEventArgs e)
        {
            _executionQueue.QueueUserWorkItem(
                new WaitCallback(
                    state =>
                    {
                        if (ProcessThrowAnException != null)
                            ProcessThrowAnException(this, e);
                    }
                ));
        }

        /// <summary>
        /// Ocorre quando o processo envia o status do processamento.
        /// </summary>
        public event EventHandler<ProcessStatusEventArgs> ProcessStatusReceived;

        protected virtual void OnProcessStatusReceived(ProcessStatusEventArgs e)
        {
            _executionQueue.QueueUserWorkItem(
                new WaitCallback(
                    state =>
                    {
                        if (ProcessStatusReceived != null)
                            ProcessStatusReceived(this, e);
                    }
                ));
        }

        /// <summary>
        /// Ocorre quando um processo é interrompido.
        /// </summary>
        public event EventHandler Aborted;

        protected virtual void OnAborted(EventArgs e)
        {
            _executionQueue.QueueUserWorkItem(
                new WaitCallback(
                    state =>
                    {
                        if (Aborted != null)
                            Aborted(this, e);
                    }
                ));
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public BaseProcess()
        {
            _inteligenceManagerGateway = new SenderGateway();
            _inteligenceManagerGateway.Connected += new EventHandler(_inteligenceManagerGateway_Connected);
            _inteligenceManagerGateway.Disconnected += new EventHandler(_inteligenceManagerGateway_Disconnected);
            this.OnReceiveMessage = OnMessageReceived;
        }

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public BaseProcess(int id)
            : this()
        {
            this.Id = id;
        }

        #endregion

        #region Connected

        void _inteligenceManagerGateway_Connected(object sender, EventArgs e)
        {
            this.OnConnected(e);
        }

        #endregion

        #region Disconnected

        void _inteligenceManagerGateway_Disconnected(object sender, EventArgs e)
        {
            this.OnDisconnected(e);
        }

        #endregion

        #region OnMessageReceived

        protected virtual void OnMessageReceived(BaseMessage message)
        {
            switch (message.MessageId)
            {
                case 1:
                    this.OnInitialized(new EventArgs());
                    break;

                case 4:
                    this.OnProcessThrowAnException(new ExceptionEventArgs(message));
                    break;

                case 5:
                    ProcessStatus processStatus = message.RetrieveParameter<ProcessStatus>();
                    this.OnProcessStatusReceived(new ProcessStatusEventArgs(processStatus));
                    break;

                case _finalizeMessageId:
                    this.OnFinalized(new EventArgs());
                    break;

                case _abortExecutedMessageId:
                    this.OnAborted(new EventArgs());
                    break;
            }
        }

        #endregion

        #region Connect

        /// <summary>
        /// Conecta com o InteligenceManager.
        /// </summary>
        public void Connect()
        {
            //try
            //{
            ValidateConnect();

            if (!_inteligenceManagerGateway.IsConnected)
            {
                _inteligenceManagerGateway.Address = RetrieveInteligenceManagerInstanceAddress();
                _inteligenceManagerGateway.Connect(Id, ClientIdentifier);
            }

            //}
            //catch (Exception ex)
            //{
            //    throw new CommunicationException("Could not connect with Inteligence Manager, see inner exceptions for more details", ex);
            //}
        }

        /// <summary>
        /// Conecta com o InteligenceManager.
        /// </summary>
        public void BeginConnect()
        {
            //try
            //{
            ValidateConnect();

            if (!_inteligenceManagerGateway.IsConnected)
            {
                _inteligenceManagerGateway.Address = RetrieveInteligenceManagerInstanceAddress();
                _inteligenceManagerGateway.BeginConnect(Id, ClientIdentifier);
            }
            //}
            //catch (Exception ex)
            //{
            //    throw new CommunicationException("Could not connect with Inteligence Manager, see inner exceptions for more details", ex);
            //}
        }

        /// <summary>
        /// Conecta com o InteligenceManager, para ouvir várias mensagens.
        /// </summary>
        /// <param name="messageId">Id da Mensagem</param>
        public void Connect(List<int> messageIds)
        {
            //try
            //{
            ValidateConnect();

            if (!_inteligenceManagerGateway.IsConnected)
            {
                _inteligenceManagerGateway.Address = RetrieveInteligenceManagerInstanceAddress();
                _inteligenceManagerGateway.Connect(messageIds);
            }
            //}
            //catch (Exception ex)
            //{
            //    throw new CommunicationException("Could not connect with Inteligence Manager, see inner exceptions for more details", ex);
            //}
        }

        /// <summary>
        /// Valida as informações obrigatórias para realizar a conexão
        /// </summary>
        private void ValidateConnect()
        {
            //validar se o Id do processo foi informado
            if (this.Id <= 0)
            {
                throw new Exception("The process Id was not informed. Please set the Id property before connect.");
            }

            if (string.IsNullOrEmpty(this.ProductId))
                throw new ArgumentNullException("The ProductId was not informed. Please set the ProductId property before connect.");

            //validar se as credenciais foram informadas
            if (this._inteligenceManagerGateway.UserCredential == null)
            {
                throw new Exception("The User Credentials was not informed. Please call the SetUserCredentials method before connect.");
            }
        }
        #endregion

        #region Disconnect
        /// <summary>
        /// Disconecta o InteligenceManager
        /// </summary>
        public void Disconnect()
        {
            if (_inteligenceManagerGateway.IsConnected)
                _inteligenceManagerGateway.Disconnect();
        }
        #endregion


        /// <summary>
        /// Envia uma mensagem para o InteligenceManager
        /// </summary>
        /// <param name="message"></param>
        protected void BeginSendMessage(BaseMessage message)
        {
            message.From = ClientIdentifier;
            _inteligenceManagerGateway.BeginSendMessage(message);
        }

        /// <summary>
        /// Envia uma mensagem para o InteligenceManager
        /// </summary>
        /// <param name="message"></param>
        protected void BeginSendMessage<T>(Message<T> message)
        {
            message.From = ClientIdentifier;
            _inteligenceManagerGateway.BeginSendMessage<T>(message);
        }

        public void SetUserCredentials(ClientCredentials credentials)
        {
            _inteligenceManagerGateway.UserCredential = credentials;
        }

        public bool IsProcessRunning()
        {
            if (!IsConnected)
                Connect();

            return _inteligenceManagerGateway.IsProcessRunning(Id);
        }

        public List<LogEntity> RetrieveLogList()
        {
            if (!IsConnected)
                Connect();

            return _inteligenceManagerGateway.RetrieveLogList(Id);
        }

        public LogEntity RetrieveLastProcessLog()
        {
            if (!IsConnected)
                Connect();

            return _inteligenceManagerGateway.RetrieveLastProcessLog(Id);
        }

        /// <summary>
        /// Recupera o endereço do InteligenceManager.
        /// </summary>
        /// <returns></returns>
        private string RetrieveInteligenceManagerAddress()
        {
            ApplicationConfiguration config = ApplicationConfiguration.GetCurrent();

            string inteligenceManagerAddress = config.GetSection<InteligenceManagerConfigurationSection>("inteligenceManagerConfiguration").InteligenceManagerAddress;

            if (String.IsNullOrEmpty(inteligenceManagerAddress))
                throw new ArgumentNullException("The issuer address configuration is not set");

            return inteligenceManagerAddress;
        }

        /// <summary>
        /// Recupera o endereço do instancia do process manager.
        /// </summary>
        /// <returns></returns>
        private string RetrieveInteligenceManagerInstanceAddress()
        {
            string instanceAddress;
            string address = RetrieveInteligenceManagerAddress();

            instanceAddress = RetrieveInstanceAddress(address);

            if (string.IsNullOrEmpty(instanceAddress))
                throw new Exception(string.Format("Process Manager has no instances configured to Product '{0}' and Process '{1}'", this.ProductId, this.Id.ToString()));
            else
                return instanceAddress;
        }

        /// <summary>
        /// Consumir o serviço de discovery do endereco da instancia
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        private string RetrieveInstanceAddress(string address)
        {
            string instanceAddress = string.Empty;
            IInteligenceManagerService proxy = null;
            try
            {
                EndpointAddress endpoint = new EndpointAddress(address);

                //BindingConfiguration
                string schema = endpoint.Uri.Scheme;

                switch (schema.ToUpper())
                {
                    case "NET.TCP":
                        NetTcpBinding tcpBinding = new NetTcpBinding(SecurityMode.None);
                        proxy = ChannelFactory<IInteligenceManagerService>.CreateChannel(tcpBinding, endpoint);
                        break;
                    case "NET.PIPE":
                        NetNamedPipeBinding namedPipeBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
                        proxy = ChannelFactory<IInteligenceManagerService>.CreateChannel(namedPipeBinding, endpoint);
                        break;
                }
                if (proxy != null)
                    instanceAddress = proxy.GetInstanceAddress(this.ProductId, this.Id);
            }
            catch
            {
                throw;
            }
            finally
            {
                try
                {
                    if (proxy != null && proxy is IClientChannel)
                        ((IClientChannel)proxy).Close();
                }
                catch { }
            }
            return instanceAddress;
        }

        #region IDisposable Members

        public void Dispose()
        {
            //Se o gateway estiver conectado, desconecta-o.
            if (_inteligenceManagerGateway != null && _inteligenceManagerGateway.IsConnected)
            {
                _inteligenceManagerGateway.Close();
            }
        }

        #endregion

        public void ThrowProcessException(Exception ex)
        {
            this.OnProcessThrowAnException(new ExceptionEventArgs(ex));
        }

        /// <summary>
        /// Inicializa o processo de Cancelamento do Processo.
        /// </summary>
        public void BeginAbort()
        {
            //Cria uma mensagem de Abort.
            Message message = new Message();
            message.MessageId = _abortMessageId;
            message.Operation = "Abort";
            message.ProcessId = this.Id;
            this.BeginSendMessage(message);
        }

    }

    public class ExceptionEventArgs : EventArgs
    {
        Exception _ex;
        public Exception Ex
        {
            get { return _ex; }
            set { _ex = value; }
        }

        BaseMessage _message;
        public BaseMessage Message
        {
            get { return _message; }
            set { _message = value; }
        }

        public ExceptionEventArgs(Exception ex)
        {
            _ex = ex;
        }

        public ExceptionEventArgs(BaseMessage message)
        {
            _message = message;
        }
    }
}
